import libtcodpy as libtcod
import utils
from random import randint
from objects import Tile, Player, Stairs, Spawner
from utils import COLORS, CHARS
from datetime import datetime

TILE_EMPTY = {
              'id': 0,
              'char': CHARS['wall'],
              'fcolor': COLORS['wall'][0],
              'bcolor': COLORS['wall'][1],
              'transparent': False,
              'walkable': False,
              'name': 'Stone Wall',
              'description': 'a wall made of stone'
              }
TILE_WALL = {
             'id': 1,
             'char': CHARS['wall'],
             'fcolor': COLORS['wall'][0],
             'bcolor': COLORS['wall'][1],
             'transparent': False,
             'walkable': False,
             'name': 'Stone Wall',
             'description': 'a wall made of stone'
             }
TILE_FLOOR = {
              'id': 2,
              'char': CHARS['floor'],
              'fcolor': COLORS['floor'][0],
              'bcolor': COLORS['floor'][1],
              'transparent': True,
              'walkable': True,
              'name': 'Dirt Floor',
              'description': 'a dirt floor'
              }
TILE_DOOR = {
             'id': 3,
             'char': CHARS['door'],
             'fcolor': COLORS['door'][0],
             'bcolor': COLORS['door'][1],
             'transparent': False,
             'walkable': True,
             'name': 'Door',
             'description': 'a wooden door'
             }

PATH_COSTS = [
              0, #Disallow path
              5, #Path through empty space
              10, #Path through walls
              1   #Path through normal floors (Increase this for increased difficulty)
              ]

DUNGEON_DEFAULTS = {
                    'min_rooms':5,
                    'max_rooms':7,
                    
                    'absolute_max_rooms':60,
                    
                    'min_size': 5,
                    'max_size':12,
                    'max_attempts':500
                    }

#===============================================================================
# DUNGEON
#===============================================================================
class Dungeon(object):
    def __init__(self, game, width = utils.MAP_RECT[2], height = utils.MAP_RECT[3], level = 0):
        self.game = game
        self.lights_on = False
        self.update_objects = True

        self.width = width
        self.height = height
        
        self.min_rooms = DUNGEON_DEFAULTS['min_rooms'] + round(level / 2)
        self.max_rooms = DUNGEON_DEFAULTS['max_rooms'] + randint(level, level * 2)
        self.max_rooms = utils.clamp(self.max_rooms, self.min_rooms + 5, DUNGEON_DEFAULTS['absolute_max_rooms'])
        self.min_size = DUNGEON_DEFAULTS['min_size']
        self.max_size = DUNGEON_DEFAULTS['max_size']
        self.max_attempts = DUNGEON_DEFAULTS['max_attempts']
        
        self.tcodmap = None
        self.tilemap = [[0 for _ in range(self.height)] for _ in range(self.width)]
        self.floor_tiles = []
        
        self.player, self.stairs_up, self.stairs_down = None, None, None
        
        self.generate()
        
    def generate(self):
        start = datetime.now()
        
        self.map_array = [[TILE_EMPTY['id'] for _ in range(self.height)] for _ in range(self.width)]
        self.rooms = []
        attempts = 0
        
        #Place rooms
        while len(self.rooms) < self.max_rooms and attempts <= self.max_attempts:
            r = self.place_room()
            if r is not False:
                self.rooms.append(r)
            attempts += 1
            
        #Regenerate if there aren't enough rooms
        if len(self.rooms) < self.min_rooms:
            self.generate()
        
        #Connect rooms using A*
        path = libtcod.path_new_using_function(self.width, self.height, self.get_path_cost, None, 0.0)
        for i in range(len(self.rooms) - 1):
            r1, r2 = self.rooms[i], self.rooms[i + 1]
            self.connect_rooms(path, r1[0] + r1[2] / 2, r1[1] + r1[3] / 2, r2[0] + r2[2] / 2, r2[1] + r2[3] / 2)
        
        #Make tilemap then TCODmap
        self.tilemap, self.floor_tiles = make_tilemap(self, self.map_array)
        self.tcodmap = make_tcodmap(self.tilemap)
        
        #Spawn Objects
        self.spawn_objects()
        
        print 'Dungeon generated in', (datetime.now() - start).microseconds / 1000.0, 'milliseconds'
    
    def place_room(self):
        rw, rh = randint(self.min_size, self.max_size), randint(self.min_size, self.max_size)
        rx, ry = randint(0, self.width - rw - 1), randint(0, self.height - rh - 1)
        for x in range(rx, rx + rw + 1):
            for y in range(ry, ry + rh + 1):
                if self.map_array[x][y] == TILE_FLOOR['id']:
                    return False
        #Success, now draw the room on the grid        
        for x in range(rx, rx + rw):
            for y in range(ry, ry + rh):
                self.map_array[x][y] = TILE_FLOOR['id']
                self.map_array[rx][y] = TILE_WALL['id']
                self.map_array[rx + rw][y] = TILE_WALL['id']
            self.map_array[x][ry] = TILE_WALL['id']
            self.map_array[x][ry + rh] = TILE_WALL['id']
        return [rx, ry, rw, rh]
    
    def get_path_cost(self, x1, y1, x2, y2, data):
        if x2 == 0 or x2 == self.width - 1 or y2 == 0 or y2 == self.height - 1:
            return PATH_COSTS[0]
        c = self.map_array[x2][y2]
        if c == TILE_EMPTY['id']:
            return PATH_COSTS[1]
        elif c == TILE_WALL['id']:
            return PATH_COSTS[2]
        else:
            return PATH_COSTS[3]
    
    def connect_rooms(self, path, x1, y1, x2, y2):
        libtcod.path_compute(path, x1, y1, x2, y2)
        for i in range(libtcod.path_size(path)):
            x, y = libtcod.path_get(path, i)
            c = self.map_array[x][y]
            if c == TILE_WALL['id']:
                doors = 0
                for j in range(-2, 3):
                    for k in range(-2, 3):
                        xx, yy = x + j, y + k
                        if xx < 0 or yy < 0 or xx >= self.width or yy >= self.height:
                            continue
                        if self.map_array[xx][yy] == TILE_DOOR['id']:
                            doors += 1
                if doors == 0 and randint(0, 1) == 0:
                    self.map_array[x][y] = TILE_DOOR['id']
                else:
                    self.map_array[x][y] = TILE_FLOOR['id']
            elif c == TILE_EMPTY['id']:
                self.map_array[x][y] = TILE_FLOOR['id']
    
    def spawn_objects(self):
        self.player = Player(self)
        self.stairs_up = Stairs(self)
        self.stairs_down = Stairs(self, False)
        
        self.spawner = Spawner(self, self.game.monster_list, self.game.item_list)
              
    def recompute_fov(self, x, y):
        libtcod.map_compute_fov(self.tcodmap, x, y, utils.FOV_RADIUS, True, utils.FOV_ALGO)
        
    def in_fov(self, x, y):
        return libtcod.map_is_in_fov(self.tcodmap, x, y)
    
    def draw(self, mx = utils.MAP_RECT[0], my = utils.MAP_RECT[1]):
        #Draw the map
        for x in range(self.width):
            for y in range(self.height):
                self.tilemap[x][y].draw()
        
        self.stairs_down.draw()
        self.stairs_up.draw()
        self.spawner.draw()
        self.player.draw()
        
    def update(self, key):
        self.player.update()
        if self.update_objects:
            self.spawner.update()
        self.update_objects = False
        
#===============================================================================
# GET TILE BY ID
#===============================================================================
def tile_from_id(id):
    if id == 1:
        return TILE_WALL
    elif id == 2:
        return TILE_FLOOR
    elif id == 3:
        return TILE_DOOR
    else:
        return TILE_EMPTY
      
#===============================================================================
# MAKE TILEMAP
#===============================================================================
def make_tilemap(map, arr):
    width = len(arr)
    height = len(arr[0])
    m = [[0 for _ in range(height)] for _ in range(width)]
    floors = []
    for x in range(width):
        for y in range(height):
            tile = tile_from_id(arr[x][y])
            m[x][y] = Tile(map, x, y, tile['char'])
            m[x][y].fcolor = tile['fcolor']
            m[x][y].bcolor = tile['bcolor']
            m[x][y].transparent = tile['transparent']
            m[x][y].walkable = tile['walkable']
            m[x][y].name = tile['name']
            m[x][y].description = tile['description']
            
            if tile == TILE_FLOOR:
                floors.append(m[x][y])
    return m, floors

#===============================================================================
# MAKE TCODMAP
#===============================================================================
def make_tcodmap(tilemap):
    width = len(tilemap)
    height = len(tilemap[0])
    m = libtcod.map_new(width, height)
    for x in range(width):
        for y in range(height):
            libtcod.map_set_properties(m, x, y, tilemap[x][y].transparent, tilemap[x][y].walkable)
    return m
    
if __name__ == "__main__":
    print "Please run main.py!"
